#include "SerialPortCommunicator.h"

#include <termios.h>
#include <linux/serial.h>
#include <sys/ioctl.h>

#include <cstring>

#include "ErrorCode.h"

SerialPortCommunicator::SerialPortCommunicator(const char * const deviceName) : DeviceCommunicator(deviceName) {	//for uart3 in_uart
    //ctor
}

SerialPortCommunicator::~SerialPortCommunicator() {
    //dtor
}

/**
 * @brief: set the properties of serial port
 * @Param: fd: file descriptor
 * @Param: nSpeed: Baud Rate
 * @Param: nBits: character size
 * @Param: nEvent: parity of serial port
 * @Param: nStop: stop bits
 */
int SerialPortCommunicator::setPort(const std::string& exceptionTip, const int nSpeed, const int nBits, const char nEvent, const int nStop) noexcept {
    termios newtio;

    std::memset(&newtio, 0, sizeof (newtio));
    /* ignore modem control lines and enable receiver */
    newtio.c_cflag|= CLOCAL | CREAD;
    newtio.c_cflag &= ~CSIZE;
    /* set character size */
    switch (nBits) {
    case 8:
        newtio.c_cflag |= CS8;
        break;
    case 7:
        newtio.c_cflag |= CS7;
        break;
    case 6:
        newtio.c_cflag |= CS6;
        break;
    case 5:
        newtio.c_cflag |= CS5;
        break;
    default:
        newtio.c_cflag |= CS8;
        break;
    }
    /* set the parity */
    switch (nEvent) {
    case 'o':
    case 'O':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag |= PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
    case 'e':
    case 'E':
        newtio.c_cflag |= PARENB;
        newtio.c_cflag &= ~PARODD;
        newtio.c_iflag |= (INPCK | ISTRIP);
        break;
    case 'n':
    case 'N':
        newtio.c_cflag &= ~PARENB;
        break;
    default:
        newtio.c_cflag &= ~PARENB;
        break;
    }
    /* set the stop bits */
    switch (nStop) {
    case 1:
        newtio.c_cflag &= ~CSTOPB;
        break;
    case 2:
        newtio.c_cflag |= CSTOPB;
        break;
    default:
        newtio.c_cflag &= ~CSTOPB;
        break;
    }
    /* set output and input baud rate */
    switch (nSpeed) {
    case 0:
        cfsetospeed(&newtio, B0);
        cfsetispeed(&newtio, B0);
        break;
    case 50:
        cfsetospeed(&newtio, B50);
        cfsetispeed(&newtio, B50);
        break;
    case 75:
        cfsetospeed(&newtio, B75);
        cfsetispeed(&newtio, B75);
        break;
    case 110:
        cfsetospeed(&newtio, B110);
        cfsetispeed(&newtio, B110);
        break;
    case 134:
        cfsetospeed(&newtio, B134);
        cfsetispeed(&newtio, B134);
        break;
    case 150:
        cfsetospeed(&newtio, B150);
        cfsetispeed(&newtio, B150);
        break;
    case 200:
        cfsetospeed(&newtio, B200);
        cfsetispeed(&newtio, B200);
        break;
    case 300:
        cfsetospeed(&newtio, B300);
        cfsetispeed(&newtio, B300);
        break;
    case 600:
        cfsetospeed(&newtio, B600);
        cfsetispeed(&newtio, B600);
        break;
    case 1200:
        cfsetospeed(&newtio, B1200);
        cfsetispeed(&newtio, B1200);
        break;
    case 1800:
        cfsetospeed(&newtio, B1800);
        cfsetispeed(&newtio, B1800);
        break;
    case 2400:
        cfsetospeed(&newtio, B2400);
        cfsetispeed(&newtio, B2400);
        break;
    case 4800:
        cfsetospeed(&newtio, B4800);
        cfsetispeed(&newtio, B4800);
        break;
    case 9600:
        cfsetospeed(&newtio, B9600);
        cfsetispeed(&newtio, B9600);
        break;
    case 19200:
        cfsetospeed(&newtio, B19200);
        cfsetispeed(&newtio, B19200);
        break;
    case 38400:
        cfsetospeed(&newtio, B38400);
        cfsetispeed(&newtio, B38400);
        break;
    case 57600:
        cfsetospeed(&newtio, B57600);
        cfsetispeed(&newtio, B57600);
        break;
    case 115200:
        cfsetospeed(&newtio, B115200);
        cfsetispeed(&newtio, B115200);
        break;
    case 230400:
        cfsetospeed(&newtio, B230400);
        cfsetispeed(&newtio, B230400);
        break;
    default:
        cfsetospeed(&newtio, B115200);
        cfsetispeed(&newtio, B115200);
        break;
    }
    /* set timeout in deciseconds for non-canonical read */
    newtio.c_cc[VTIME] = 2;//TODO
    /* set minimum number of characters for non-canonical read */
    newtio.c_cc[VMIN] = 0;
    /* flushes data received but not read */
    int result = tcsetattr(deviceFd, TCSANOW, &newtio);
    if (0 != result) {
        std::string prefix{"deviceName:"};
        prefix.append(deviceName);
        prefix.append(",deviceFd:");
        prefix.append(std::to_string(deviceFd));
        prefix.append(",");
        prefix.append(exceptionTip);
        prefix.append(",setPort");
        LOG_ERROR(buildErrorMessage(result, errno, prefix).data());
    }

    return result;
}



/**
 * @brief: main function
 * @Param: argc: number of parameters
 * @Param: argv: parameters list
 */
int SerialPortCommunicator::switchRs485(const bool enable) noexcept{
    serial_rs485 rs485conf;
    int result;

    /* Get configure from device */
    result = ioctl(deviceFd, TIOCGRS485, &rs485conf);
    if (0 > result) {
        std::string prefix{"Ioctl error on getting 485 configure:"};
        LOG_ERROR(buildErrorMessage(result, errno, prefix).data());
    }

    /* Set enable/disable to configure */
    if (enable) {   // Enable rs485 mode
        rs485conf.flags |= SER_RS485_ENABLED;
    } else {        // Disable rs485 mode
        rs485conf.flags &= ~(SER_RS485_ENABLED);
    }

    rs485conf.delay_rts_before_send = 1;//4
    rs485conf.delay_rts_after_send = 0;

    /* Set configure to device */
    result = ioctl(deviceFd, TIOCSRS485, &rs485conf);
    if (0 > result) {
        std::string prefix{"Ioctl error on setting 485 configure:"};
        LOG_ERROR(buildErrorMessage(result, errno, prefix).data());
    }

    return result;
}






/******************************************************************************
 * NAME:
 * read
 *
 * DESCRIPTION:
 * read data from serial port
 *
 *
 * PARAMETERS:
 * buffer - The buffer to keep data
 * length - The count of data
 * time - time out
 *
 * RETURN:
 * Count of data wrote
 ******************************************************************************/
ssize_t SerialPortCommunicator::read(const std::string& exceptionTip, void * buffer, const size_t length) noexcept {

    ssize_t result = 0;
    if (length < 1){
        return result;
    }

    ssize_t readed = 0;
     uint8_t* const start = static_cast<uint8_t*> (buffer);
     uint8_t* iterator = start;
     uint8_t* const endIterator = iterator + length;
    do {
        readed = ::read(deviceFd, iterator, sizeof(uint8_t));
//        LOG_DEDUG("readed:%zd,[%u,%c]", readed, *iterator, *iterator);
    } while (0 < readed && '<' != *iterator);
    if(0 >= readed) {
        LOG_ERROR("begining readed size[%zd]!", readed);
        result = -1;
    } else {
        while(endIterator != ++iterator) {
            readed = ::read(deviceFd, iterator, sizeof(uint8_t));//
            if(0 >= readed) {
                LOG_ERROR("response text readed size[%zd]!", readed);
                result = -1;
                break;
            }
            //printf("recv data: %c\n", read_byte);

            if('>' == *iterator) {
                result = iterator - start + 1;
                break;
            }
        }
        if (endIterator <= iterator){//buffer is too small
            result = -2;
        }
    }

    return result;
}

